Floating Point

Boreal

So far we've seen how to deal with integers in the range -2,147,483,648 through +2,147,483,647, but sometimes we need to deal with fractional numbers and very large or very small numbers.

For instance, suppose you had 123 shares of stock that were trading at 42 7/8. How much are they worth? Obviously you calculate:

        123 * (42+7/8)
        123 * (42+.875)
        123 * 42.875
        5273.625
        $5,273.63

Here's how to do it in assembly language:

                fld     n7              ;load 7.0 (onto FPU stack)
                fdiv    n8              ;divide it by 8.0
                fadd    n42             ;add 42.0
                fmul    n123            ;multiply by 123.0
                . . .
        n7      dq      7.0
        n8      dq      8.0
        n42     dq      42.0
        n123    dq      123.0

Floating point instructions have no immediate address mode, like integer instructions have. We can't write:

                fld     7.0             ;load 7.0 (onto FPU stack)
                fdiv    8.0             ;divide it by 8.0
                . . .

Instead constants must be fetched from memory as though they were variables.

Floating point values are usually defined using the "dq" (Define Quad word) directive. This sets up four words, or eight bytes. The decimal point in "7.0" tells the assembler to use the floating point form instead of an integer.

Eight bytes is sufficient to hold a 16-digit number along with an exponent that specifies where the decimal point goes. Numbers are stored in what amounts to scientific notation. They can range from the very small value of 2.23*10^-308 to the very large value of 1.79*10^308. Exponential notation is used to write these values like this: 2.23E-308 and 1.79E+308.

Floating point numbers can be stored in single, double, and extended precision. Double precision is the one best matched to the Floating Point Unit (FPU) hardware (like 32-bit precision is best for integers) so it's the one we'll use. This is what the "dq" directive does. (The FPU can also use 16, 32, and 64-bit integers, and 10-digit binary coded decimal - BCD.)

Once upon a time programmers went to great lengths to avoid floating point calculations because they were very slow and cumbersome compared to integer calculations. With the Pentium that's no longer true. In fact a floating point multiply is more than three times faster than an integer multiply (3 cycles versus 10).

The FPU contains eight floating point registers arranged as a stack:

                st(0)   <-- top of stack
                st(1)
                st(2)
                st(3)
                st(4)
                st(5)
                st(6)
                st(7)

In our example, FLD N7 pushes 7.0 onto the stack into st(0). FDIV N8 divides the value in st(0) by 8.0 (giving 0.875), and FADD N42 adds 42.0 to it, giving 42.875 in st(0).

This is enough to get you started doing floating point calculations. The FPU instructions are listed on page 4 of PCASM.TXT. Essential descriptions of these instructions are in chapter 5 of Microsoft Assembler Reference. FLOUT.ASM (FLoat OUT - in ASM101.ZIP) is a routine that will display your results. It also provides an extensive example of floating point code. Please give it a look.

Conclusion

This pretty well wraps it up for ASM-101. Hopefully you're now writing programs in assembly language.

There are some additional examples in the bonus pack to inspire you. They have been painstakingly commented to make them easier to understand. GDEMO.ASM shows how to make a lively graphic demo with just 53 bytes. MUSIC.ASM makes beautiful music - or at least the best that can be expected from the PC's beeper speaker. MATRIX2.ASM is the second version of a great intro by G3 - the first version was presented in Hugi Compo 22. (Many similar intros can be downloaded from: 256b.com.) (Windows XP users: Please run MATRIX2 from a DOS prompt in full-screen mode.)

Happy assembling!

Boreal (aka: Loren Blaney)